home *** CD-ROM | disk | FTP | other *** search
- Submitted-by: jsh@teal.csn.org (Jeffrey Haemer)
-
- Folks,
-
- A few weeks back, we posted a note asking for your advice and aid
- in prototyping software for electronic standards balloting. We
- got it. Thanks. Now we want some more.
-
- Here's a shar with a first cut at some balloting software. Unshar
- it, read the README, and the rest should be self-explanatory.
- We're looking for advice, bugs, bug-fixes, enhancements, and users.
- The shar includes our original post, explaining our goal in more detail.
-
- Jeffrey S. Haemer, USENIX Standards Liaiason <jsh@usenix.org>
- Pat Wilson, SAGE Board Member <paw@rigel.dartmouth.edu>
-
- ========
- #! /bin/sh
- # This is a shell archive. Remove anything before this line, then unpack
- # it by saving it into a file and typing "sh file". To overwrite existing
- # files, type "sh file -c". You can also feed this as standard input via
- # unshar, or by typing "sh <file", e.g.. If this archive is complete, you
- # will see the following message at the end:
- # "End of archive 1 (of 1)."
- # Contents: Environ.pl Mail.pl README ballot balloting.mm vote
- # Wrapped by ballot@canary.com on Fri Jun 4 14:11:16 1993
- PATH=/bin:/usr/bin:/usr/ucb ; export PATH
- if test -f 'Environ.pl' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'Environ.pl'\"
- else
- echo shar: Extracting \"'Environ.pl'\" \(1773 characters\)
- sed "s/^X//" >'Environ.pl' <<'END_OF_FILE'
- X# $Id: Environ.pl,v 3.0 1993/06/04 19:53:34 ballot Exp ballot $
- X
- Xpackage Environ;
- X# print the environment
- Xsub 'printenv {
- X foreach (sort keys %ENV) {
- X print "$_=$ENV{$_}\n";
- X }
- X}
- X
- X# set environment variables
- X# override and add to the values in ENV
- X# with values specified in a resource file.
- X# The resource file is specified by RESOURCE
- Xsub 'setenv {
- X local(@path) = split(m%/%, $0);
- X local($cmd) = pop(@path);
- X %ENV = &'get_arr($cmd, %ENV) unless ($'setenv'guard++)
- X}
- X
- X# fill an array from a specified file
- X# Use the first argument as the name of an environment variable
- X# that points at the keyfile.
- X# If that fails, try a file by the name of arg1
- X# Use the second argument as an array of default values
- X#
- X# See setenv() as an example.
- X#
- X# BUGS:
- X# I don't get why I can't combine the lines marked with "?".
- X#
- Xsub 'get_arr {
- X local($keyfile) = shift(@_);
- X local (%arr) = @_;
- X open(KEYS, $ENV{$keyfile} = $ENV{$keyfile} ? $ENV{$keyfile} : ("$keyfile.res") ) || return %arr;
- X while (<KEYS>) {
- X chop;
- X ($name, $other) = split(' ', $_, 2); # ?
- X $arr{$name} = $other; # ?
- X }
- X close(KEYS) || die "can't close $keyfile: $!, aborting\n";
- X return %arr;
- X}
- X
- X# Test routine. Invoke it in a driver with
- X# require "Environ.pl";
- X# &Environ'test;
- Xsub test {
- X &'setenv;
- X %arr = &'get_arr("foo", %ENV);
- X die "get_arr failed to get ENV\n" unless $arr{HOME} = $ENV{HOME};
- X $arr{HOME} = NULL;
- X
- X open(IN, "foo.res");
- X print IN "HOME\tBOGUS\n";
- X close(IN);
- X %arr = &'get_arr("foo", %ENV);
- X die "getarr failed to read foo.res\n" unless $arr{HOME} = "BOGUS";
- X $arr{HOME} = NULL;
- X
- X $ENV{"bar"} = "foo.res";
- X %arr = &'get_arr("bar", %ENV);
- X die "getarr failed to read ENV{bar}\n" unless $arr{HOME} = "BOGUS";
- X $arr{HOME} = NULL;
- X
- X unlink("foo.res");
- X print "Package tests okay.\n";
- X}
- X
- X1;
- END_OF_FILE
- if test 1773 -ne `wc -c <'Environ.pl'`; then
- echo shar: \"'Environ.pl'\" unpacked with wrong size!
- fi
- chmod +x 'Environ.pl'
- # end of 'Environ.pl'
- fi
- if test -f 'Mail.pl' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'Mail.pl'\"
- else
- echo shar: Extracting \"'Mail.pl'\" \(2375 characters\)
- sed "s/^X//" >'Mail.pl' <<'END_OF_FILE'
- X# $Id: Mail.pl,v 3.0 1993/06/04 19:53:34 ballot Exp ballot $
- X
- Xrequire "Environ.pl";
- X
- Xpackage Mail;
- X
- X@MAILER = ("/usr/bin/mailx", "/bin/mail", "/usr/bin/mail", "/usr/ucb/Mail");
- X
- X# get key lines
- X# Return each key value
- X# in the current item in an associative array.
- X# keylines are specified by regular expressions
- X# in a file named by the second argument,
- X# default expressions are supplied in the third.
- Xsub 'getkeys {
- X
- X local($_, $keyfile, %defaults) = @_;
- X
- X %keyline = &'get_arr($keyfile, %defaults) unless ($getkeys'guard++);
- X local(%info);
- X local($*) = 1;
- X for $k (keys %keyline) {
- X $info{$k} = $1 if (/$keyline{$k}/);
- X }
- X return %info;
- X}
- X
- X# get all the mail messages
- X# Toss the entire mail file into an array, one element per message.
- X# Return the array
- Xsub 'getmsgs {
- X open(INBOX, $_[0]) || die "can't open $_[0]: $!, aborting\n";
- X local($/) = '';
- X @msg = ();
- X $msg = '';
- X while(<INBOX>) {
- X if (/^From /) {
- X push(@msg, $msg) if $msg;
- X $msg = '';
- X }
- X $msg .= $_;
- X }
- X push (@msg, $msg) if $msg;
- X return @msg;
- X}
- X
- X# mail a message
- X# just a call to the native mail program
- X#
- X# BUGS: I worry a bit about the diversity of mail programs
- Xsub 'mailx {
- X die "bad call to mailx\n" unless @_ > 2; # try assert
- X local($sub, $to, @msg) = @_;
- X ($ENV{MAILER}) = grep(-x, @MAILER) unless $ENV{MAILER};
- X $cmd = "| $ENV{MAILER} -s '$sub' $to";
- X open(ACKMAIL, $cmd) || die "can't open $cmd: $!, aborting\n";
- X print ACKMAIL @msg;
- X close(ACKMAIL) || die "can't close $cmd: $!, aborting\n";
- X}
- X
- X# Test routine. Invoke it in a driver with
- X# require "Mail.pl";
- X# &Mail'test;
- X
- X# default values. %KEYLINES is probably the wrong set
- X@MAILDIR = ("/var/spool/mail", "/var/mail", "/usr/spool/mail");
- X%KEYLINE = (
- X 'Checksum', 'Checksum:\s*(\d+\s*\d+)',
- X 'From', '^From:\s*(.*)',
- X 'Author', 'Author:\s*(.*)',
- X 'ID', 'ID:\s*(\d+)',
- X 'Subject', '^Subject:\s*(.*)',
- X 'Vote', 'Vote:\s*(.+)',
- X );
- X
- Xsub test {
- X setpwent;
- X ($name) = getpwuid($>); # I dunno, why not?
- X endpwent;
- X &'mailx("Zzazz", $name, "This is the first line\nThis is the second\n");
- X sleep 10;
- X ($maildir) = grep(-e, @MAILDIR);
- X @msgs = &'getmsgs("$maildir/$name");
- X $newmsg = pop(@msgs);
- X %info = &'getkeys($newmsg, "keyline", %KEYLINE);
- X
- X die "Bad subject line $info{Subject}\n" if "Zzazz" != $info{Subject};
- X die "Bad recipient line $info{To}\n" if "Zzazz" != $info{To};
- X print "Package tests okay.\n";
- X}
- X
- X1;
- END_OF_FILE
- if test 2375 -ne `wc -c <'Mail.pl'`; then
- echo shar: \"'Mail.pl'\" unpacked with wrong size!
- fi
- # end of 'Mail.pl'
- fi
- if test -f 'README' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'README'\"
- else
- echo shar: Extracting \"'README'\" \(4209 characters\)
- sed "s/^X//" >'README' <<'END_OF_FILE'
- X# $Id: README,v 3.0 1993/06/04 19:53:34 ballot Exp ballot $
- X
- XThis is a cut at balloting software.
- X
- X
- X INSTALLATION
- X
- XThe system receiving the votes should have a special id to process
- Xthe votes, and votes should be sent to that id (e.g.
- X<posix-dot-fifty@canary.com>).
- X
- XYou _must_ create an initial, two-column file of valid voters,
- X"ids.res". Each voter needs an id, and must know that id to vote.
- X(The format of the file is "ID Votername".) For IEEE standards
- Xballots, for example, you might want to make the list be a list of
- XIEEE members and their IEEE membership numbers.
- X
- XThe system has reasonable defaults, but it's fairly configurable;
- Xit lets you use external files to specify what kinds of votes can
- Xbe cast, where the mailboxes are, and all sorts of other things.
- XUnfortunately, you currently have to read the code to understand
- Xhow to configure it.
- X
- XCurrently, the scripts point at /usr/bin/perl. If your system has
- Xperl someplace else, you'll get non-informative error messages.
- X
- X
- X WHAT IT DOES
- X
- XVoters run "vote" to cast ballots. The recipient of the votes runs
- X"ballot" to count ballots.
- X
- X"vote" insures that the ballot has all the right fields,
- Xthen tags a checksum on the end.
- X
- X"ballot" runs through the incoming mailbox and sorts the votes into
- Xbins. In doing so, it validates the balloter and the checksum,
- Xand sends the sender an acknowledgment of the receipt of the vote.
- X
- X
- X WHAT IT DOESN'T DO: KNOWN PROBLEMS/PROJECTS
- X
- XThere should be a good configuration and installation script.
- X
- XCurrently, the scripts point at /usr/bin/perl. If your system has
- Xperl someplace else, you'll get non-informative error messages.
- XThis could probably be fixed with a good configuration and
- Xinstallation script.
- X
- XOther software that needs to be written are a utility for tallying
- Xvotes and a utility for processing invalid votes. We have contributed
- Xsoftware that parses valid votes once they're binned, but it's not yet
- Xready to post.
- X
- XThe "ids.res" file (and its processing) could be more sophisticated.
- XIn particular, if it contained an email address, we could have a
- Xdaemon periodically run through the file and send mail noodging
- Xthose who haven't yet voted.
- X
- XKnown problems awaiting future solution are indicated in comments
- Xthe code. Those problems should probably be listed here instead,
- Xor at least duplicated here.
- X
- XCurrently, the first vote from a voter that shows up in the mailbox
- Xis the one that counts. This has two shortcomings. First, if
- Xthe messages arrive out-of-order, this isn't the right choice.
- XSecond, we might want to allow people to change their minds, in
- Xwhich case we should probably use the _last_ message that arrives.
- XPerhaps this should be a configuration or command-line option.
- X
- XThere should be a man page that doesn't make you read the code to
- Xfigure out how to configure things.
- X
- X"ballot" doesn't make any effort to lock the input mailbox. That's
- Xbecause I don't really know how to do this. It also doesn't do
- Xvery fancy security, for the same reason. (Currently, "vote" appends
- Xa simple checksum to the message which is double-checked by "ballot",
- Xand then returned to the sender so that the sender can be certain that
- Xthe checksum received was the one sent.)
- X
- XBecause different groups may want different security levels or
- Xalgorithms, perhaps the security/checksumming should be done by a
- Xseparate executable that "vote" and "ballot" call as a subprocess.
- X
- X
- X PHILOSOPHICAL OBSERVATION
- X
- XIt is arguably ironic that this balloting software, initially
- Xdesigned to help ballot standards documents, is in a non-standardized
- Xlanguage. I suspect that the fastest way to whip this suite into
- Xshape is to ask that everyone who chooses to comment on this amusing
- Xirony accompany the comment with an improvement to the software.
- X(Figure, it's either that or we create a group to standardize perl ... :-)
- X
- X
- X FILES
- X
- XEnvPkg.pl A package to get and set environment variables and resources
- XMailPkg.pl A package that handles the mail messages
- XREADME This file
- Xballot Sorts mail file into bins of each kind of vote
- Xids.res A two-column list of folks yet to vote: ID Name
- Xvote script to generate a vote
- Xvoted.res Folks who have already voted, same format as "ids"
- END_OF_FILE
- if test 4209 -ne `wc -c <'README'`; then
- echo shar: \"'README'\" unpacked with wrong size!
- fi
- # end of 'README'
- fi
- if test -f 'ballot' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'ballot'\"
- else
- echo shar: Extracting \"'ballot'\" \(4819 characters\)
- sed "s/^X//" >'ballot' <<'END_OF_FILE'
- X#!/usr/bin/perl
- X# $Id: ballot,v 3.0 1993/06/04 19:53:34 ballot Exp ballot $
- X
- Xrequire "Mail.pl";
- X
- X@MAILDIRS = ("/var/spool/mail", "/var/mail", "/usr/spool/mail");
- X%CHOICES = (
- X YES, "yes",
- X NO, "no",
- X ABSTAIN, "abstain",
- X);
- X
- X%KEYLINE = (
- X 'Checksum', 'Checksum:\s*(\d+\s*\d+)',
- X 'From', '^From:\s*(.*)',
- X 'Author', 'Author:\s*(.*)',
- X 'ID', 'ID:\s*(\d+)',
- X 'Subject', '^Subject:\s*(.*)',
- X 'Vote', 'Vote:\s*(.+)',
- X );
- X
- X# acknowledge a message
- X# parse out the sender,
- X# return an ack with enough information to let the recipient know
- X# what we actually got.
- Xsub ack {
- X local($sub) = "Vote received";
- X local($to) = $Keyline{From};
- X local(@ack) = (
- X "Received vote: $Keyline{Vote}\n",
- X "Voter: $Keyline{Author}\n",
- X "ID: $Keyline{ID}\n",
- X "Checksum: $Keyline{Checksum}\n",
- X "Msg subject: $Keyline{Subject}\n"
- X );
- X
- X # optional "To" line formats
- X if ($to =~ /<(\S*)>/) {
- X $to = $1;
- X } elsif ($to =~ /(\S*)\s*\(.*\)/) {
- X $to = $1;
- X } else {
- X return 0;
- X }
- X &mailx($sub, $to, @ack);
- X 1;
- X}
- X
- X# 'bin' an individual message
- X# Make sure the vote's legit and acknowledge it,
- X# then toss the message into the bin
- X# indicated in the message's "Vote:" field.
- Xsub bin {
- X local($msg) = @_[0];
- X %Keyline = &'getkeys($msg, "keyline", %KEYLINE);
- X local($vote) = $Keyline{Vote};
- X foreach (keys %Choices) {
- X $vote = $_, last if $vote =~ /$Choices{$_}/i
- X }
- X $vote = BAD unless (&valid($msg) && $vote && &ack());
- X $tally{$vote}++;
- X print $vote $msg;
- X}
- X
- X# check out the checksum
- X# Right now, expects messages to end with the two lines
- X# Checksum: nnnn nnnn
- X#
- X# where the numbers come from running cksum
- X# on the rest of the message body
- X#
- X# BUGS: Okay, okay, this isn't any good.
- X# Still, little point wasting time here
- X# until I know what the right thing to do is.
- Xsub cksum {
- X local($msg) = @_[0];
- X return 1;
- X # $*=1;
- X @text = split(/\n\n/, $msg); # split out the header
- X shift(@text); # and dump it
- X @text = split(/^Checksum: /, join(' ', @text)); # now the get the checksum
- X $checksum = pop(@text);
- X $checksum = unpack("%32C*", join(' ', @text));
- X}
- X
- X# clean up
- X# print out the tally;
- X# rewrite the "ids" (yet-to-vote) and "voted" files;
- X# finally close open files
- X# removing any that got created but never used
- Xsub clean_up {
- X &tally(%tally);
- X open(IDS, "> $ENV{'ids'}") || die "can't open $ENV{ids} for write: $!, aborting\n";
- X open(VOTED, ">> $ENV{'voted'}") || die "can't open $ENV{voted} for append: $!, aborting\n";
- X foreach (keys %Ids) {
- X print { $Voted{$_} ? VOTED : IDS } "$_\t$Ids{$_}\n";
- X }
- X close(IDS) || die "can't close $ENV{ids}: $!, aborting\n";
- X close(VOTED) || die "can't close $ENV{voted}: $!, aborting\n";
- X close(BAD) || die "can't close BAD: $!, aborting";
- X foreach (BAD, $ENV{"ids"}, $ENV{"voted"}) {
- X unlink $_ if -z $_;
- X }
- X
- X unlink("BAD") if -z "BAD";
- X foreach (keys %Choices) {
- X close($_) || die "can't close $_: $!, aborting";
- X unlink $_ if -z $_;
- X }
- X}
- X
- X# all the initialization
- X# open up the input mailbox and the output mailboxes.
- X# note that votes are cumulative
- X#
- X# BUGS: needs to lock the input mailbox,
- X# then move it out of the way so ballots aren't counted twice
- Xsub initialize {
- X &setenv;
- X die "No valid voters\n" unless %Ids = &get_arr("ids");
- X local(%voted) = &get_arr("voted");
- X foreach (keys %voted) {
- X $Voted{$_}++;
- X }
- X
- X ($Maildir) = grep(-e, @MAILDIRS);
- X ($Name) = getpwuid($>); # I dunno, why not?
- X $ENV{BALLOT_BOX} = $ENV{BALLOT_BOX} || "$Maildir/$Name";
- X $ENV{"voted"} = $ENV{"voted"} || "voted.res";
- X die "No votes\n" if ( -z $ENV{BALLOT_BOX} || ! -e $ENV{BALLOT_BOX} );
- X %Choices = &get_arr("choices", %CHOICES);
- X foreach (keys %Choices) {
- X open($_, ">> $_") || die "can't open $_ for append: $!, aborting\n";
- X }
- X open(BAD, ">> BAD") || die "can't open BAD for append: $!, aborting\n";
- X}
- X
- X# tally the votes
- X# just print out the tallies from this batch of mail
- X#
- X# BUGS: This is just a debugging tool.
- X# Tallying should be done by a separate process
- X# and go directly to the tally bins.
- Xsub tally {
- X local(%tally) = @_;
- X foreach (sort keys %tally) {
- X print "$_=$tally{$_}\n";
- X }
- X}
- X
- X# validate the message
- X# Read the file that matches IDs to voter names,
- X# then check that the message "ID:" field has the right "Name:" field.
- X# and that the checksum matches the message.
- X# Don't allow ballot-box stuffing -- only vote once.
- X#
- X# BUGS: May need other checks.
- X#
- X# Assumes access to a global %Keyline array,
- X# which is probably the wrong thing to do.
- X# This should all be modularized and stuff.
- Xsub valid {
- X local($msg) = @_[0];
- X ++$Voted{$Keyline{ID}} if
- X ($Keyline{Author} eq $Ids{$Keyline{ID}})
- X && &cksum($msg)
- X && !$Voted{$Keyline{ID}};
- X}
- X
- X# read mail messages from mailbox
- X# and toss them one-by-one into an appropriate output mailbox
- X
- X&initialize;
- X@msg = &getmsgs($ENV{BALLOT_BOX});
- Xwhile($msg = shift(@msg)) {
- X &bin($msg);
- X}
- X&clean_up;
- END_OF_FILE
- if test 4819 -ne `wc -c <'ballot'`; then
- echo shar: \"'ballot'\" unpacked with wrong size!
- fi
- chmod +x 'ballot'
- # end of 'ballot'
- fi
- if test -f 'balloting.mm' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'balloting.mm'\"
- else
- echo shar: Extracting \"'balloting.mm'\" \(1852 characters\)
- sed "s/^X//" >'balloting.mm' <<'END_OF_FILE'
- X.\" $Id: balloting.mm,v 1.1 1993/06/04 20:10:15 ballot Exp ballot $
- X.\" Uses the mm macro ".PH", but that's all
- X.PH "''''"
- X.ce
- XElectronic Balloting Software
- X
- XWhat if you could FTP POSIX draft standards and ballot on them from
- Xthe comfort of your own keyboard? No more messy stamps or ink-stained
- Xfingers! No need to wait on the Postal "Service" for the latest
- Xdrafts!
- X
- XWe see a future in which draft standards will be available for
- Xanonymous FTP, and balloting can be done by email. For a variety
- Xof reasons -- some sensible, some merely historical -- achieving
- Xeither of these advances requires overcoming both political and
- Xtechnical barriers. We'd like to remove the technical barriers.
- XAndrew Hume has already demonstrated a prototype solution for
- Xelectronic draft distribution. We'd like to see a prototype for
- Xelectronic balloting, and we'd like your help to make this happen.
- X
- XIt's envisioned that any electronic balloting procedure will need
- X(a) authentication at least as good as Snail Mail provides and (b)
- Xvote-counting software to make it as painless as possible.
- X
- XQuick-and-dirty authentication can be as simple as (1) mail the
- Xballot-request software, which then (2) generates an encryption
- Xkey and (3) sends it, via postal mail (wouldn't want them to feel
- X_too_ left out!) to the requester, who then (4) uses the out-of-band
- Xkey to encrypt the ballot, which is then (5) decrypted by the
- Xballoting software and (6) counted appropriately -- or at least
- Xthat's the plan.
- X
- XWhat's wrong with the plan? How can it be made better? How
- Xinteresting can the vote-counting software get? Should it be
- Xwritten in perl?
- X
- XPlease contribute your thoughts, code, etc., and help POSIX balloting
- Xstep into the 1990s.
- X
- X
- X.nf
- X- Jeffrey S. Haemer, USENIX Standards Liaison <jsh@usenix.org>
- X- Pat Wilson, SAGE Board Member <paw@rigel.dartmouth.edu>
- X.fi
- END_OF_FILE
- if test 1852 -ne `wc -c <'balloting.mm'`; then
- echo shar: \"'balloting.mm'\" unpacked with wrong size!
- fi
- # end of 'balloting.mm'
- fi
- if test -f 'vote' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'vote'\"
- else
- echo shar: Extracting \"'vote'\" \(836 characters\)
- sed "s/^X//" >'vote' <<'END_OF_FILE'
- X#!/usr/bin/perl
- X# $Id: vote,v 3.0 1993/06/04 19:53:34 ballot Exp ballot $
- X
- X# send in a ballot
- X# Tags a checksum on the end.
- X#
- X# BUGS:
- X# The program should probably save the checksum,
- X# or even the completed ballot, somewhere
- X# so the user can verify that the acknowledgement
- X# matches what was sent.
- X# Right now, I use mail's "set record=" feature for this.
- X
- Xrequire "Mail.pl";
- X
- X@KEYWORDS = (Author, ID, Vote);
- X
- Xdie "Usage $0 ballot address\n" if (@ARGV != 2);
- Xopen(BALLOT, $ARGV[0]);
- Xwhile (<BALLOT>) {
- X push (@msg, $_);
- X foreach $k (@KEYWORDS) {
- X $keywords{$k}++ if ($_ =~ /^$k/);
- X }
- X}
- X
- Xforeach $k (@KEYWORDS) {
- X unless ($keywords{$k}) {
- X print "$k? ";
- X $_ = <>;
- X redo if $_ =~ /^\s+$/;
- X push(@msg, "$k: $_");
- X next;
- X }
- X}
- Xpush (@msg, "Checksum: " . unpack('%32C*', join('', @msg)) . "\n");
- X
- X&mailx("Vote", $ARGV[1], @msg);
- END_OF_FILE
- if test 836 -ne `wc -c <'vote'`; then
- echo shar: \"'vote'\" unpacked with wrong size!
- fi
- chmod +x 'vote'
- # end of 'vote'
- fi
- echo shar: End of archive 1 \(of 1\).
- cp /dev/null ark1isdone
- MISSING=""
- for I in 1 ; do
- if test ! -f ark${I}isdone ; then
- MISSING="${MISSING} ${I}"
- fi
- done
- if test "${MISSING}" = "" ; then
- echo You have the archive.
- rm -f ark[1-9]isdone
- else
- echo You still need to unpack the following archives:
- echo " " ${MISSING}
- fi
- ## End of shell archive.
- exit 0
-
-
- Volume-Number: Volume 31, Number 74
-
-